#include "Application.h"

#include "KeyboardMovementController.h"
#include "Buffer.h"
#include "Camera.h"

#include "systems/PointLightSystem.h"
#include "systems/SimpleRenderSystem.h"

#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <glm/glm.hpp>
#include <glm/gtc/constants.hpp>
#include <stdexcept>
#include <cassert>
#include <chrono>
#include <array>

namespace vkengine
{

	Application::Application() 
	{
        m_GlobalPool =
            DescriptorPool::Builder(m_Device)
            .SetMaxSets(SwapChain::MAX_FRAMES_IN_FLIGHT)
            .AddPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, SwapChain::MAX_FRAMES_IN_FLIGHT)
            .Build();
        LoadGameObjects();
	}

	Application::~Application() {}

	void Application::Run()
	{
        std::vector<std::unique_ptr<Buffer>> uboBuffers(SwapChain::MAX_FRAMES_IN_FLIGHT);
        for (int i = 0; i < uboBuffers.size(); i++) {
            uboBuffers[i] = std::make_unique<Buffer>(
                m_Device,
                sizeof(GlobalUbo),
                1,
                VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
                VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
            uboBuffers[i]->Map();
        }

        auto globalSetLayout =
            DescriptorSetLayout::Builder(m_Device)
            .AddBinding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_ALL_GRAPHICS)
            .Build();

        std::vector<VkDescriptorSet> globalDescriptorSets(SwapChain::MAX_FRAMES_IN_FLIGHT);
        for (int i = 0; i < globalDescriptorSets.size(); i++) {
            auto bufferInfo = uboBuffers[i]->DescriptorInfo();
            DescriptorWriter(*globalSetLayout, *m_GlobalPool)
                .WriteBuffer(0, &bufferInfo)
                .Build(globalDescriptorSets[i]);
        }

        SimpleRenderSystem simpleRenderSystem{ m_Device,m_Renderer.GetSwapChainRenderPass(),globalSetLayout->GetDescriptorSetLayout() };
        
        PointLightSystem pointLightSystem{
            m_Device,
            m_Renderer.GetSwapChainRenderPass(),
            globalSetLayout->GetDescriptorSetLayout() };

        Camera camera{};
        camera.SetViewTarget(glm::vec3(-1.f, -2.f, -2.f), glm::vec3(0.f, 0.f, 2.5f), glm::vec3(0.f, 1.0f, 0.0f));

        auto viewerObjecet = GameObject::CreateGameObject();
        viewerObjecet.transform.translation.z = -2.5f;
        KeyboardMovementController cameraController;

        auto currentTime = std::chrono::high_resolution_clock::now();

		while (!m_Window.ShouldClose())
		{
			glfwPollEvents();
			
            auto newTime = std::chrono::high_resolution_clock::now();
            float frameTime = std::chrono::duration<float, std::chrono::seconds::period>(newTime - currentTime).count();
            currentTime = newTime;

            cameraController.MoveInPlaneXZ(m_Window.GetGLFWwindow(), frameTime, viewerObjecet);
            camera.SetViewYXZ(viewerObjecet.transform.translation, viewerObjecet.transform.rotation);

            float aspect = m_Renderer.GetAspectRation();

            // camera.setOrthographicProjection(-aspect, aspect, -1, 1, -1, 1);
            camera.SetPerspectiveProjection(glm::radians(50.f), aspect, 0.1f, 100.f);

            if (auto commandBuffer = m_Renderer.BeginFrame())
            {
                int frameIndex = m_Renderer.GetFrameIndex();
                FrameInfo frameInfo{ frameIndex,frameTime,commandBuffer,camera ,globalDescriptorSets[frameIndex],m_GameObjects};

                // update
                GlobalUbo ubo{};
                ubo.projection = camera.GetProjection();
                ubo.view = camera.GetView();
                ubo.inverseView = camera.GetInverseView();
                pointLightSystem.Update(frameInfo, ubo);
                uboBuffers[frameIndex]->WriteToBuffer(&ubo);
                uboBuffers[frameIndex]->Flush();

                m_Renderer.BeginSwapChainRenderPass(commandBuffer);
                simpleRenderSystem.RenderGameObjects(frameInfo);
                pointLightSystem.Render(frameInfo);
                m_Renderer.EndSwapChainRenderPass(commandBuffer);
                m_Renderer.EndFrame();
            }
		}
        vkDeviceWaitIdle(m_Device.Vkdevice());
	}

    // temporary helper function, creates a 1x1x1 cube centered at offset
  

    void Application::LoadGameObjects()
    {
        std::shared_ptr<Model> model = Model::CreateModelFromFile(m_Device, "models/flat_vase.obj");
        auto flatVase = GameObject::CreateGameObject();
        flatVase.model = model;
        flatVase.transform.translation = { -.5f, .5f, 0.f };
        flatVase.transform.scale = { 3.f, 1.5f, 3.f };
        m_GameObjects.emplace(flatVase.GetId(), std::move(flatVase));

        model = Model::CreateModelFromFile(m_Device, "models/smooth_vase.obj");
        auto smoothVase = GameObject::CreateGameObject();
        smoothVase.model = model;;
        smoothVase.transform.translation = { .5f, .5f, 0.f };
        smoothVase.transform.scale = { 3.f, 1.5f, 3.f };
        m_GameObjects.emplace(smoothVase.GetId(), std::move(smoothVase));

        model= Model::CreateModelFromFile(m_Device, "models/quad.obj");
        auto floor = GameObject::CreateGameObject();
        floor.model = model;
        floor.transform.translation = { 0.f, .5f, 0.f };
        floor.transform.scale = { 3.f, 1.f, 3.f };
        m_GameObjects.emplace(floor.GetId(), std::move(floor));

        std::vector<glm::vec3> lightColors{
             {1.f, .1f, .1f},
             {.1f, .1f, 1.f},
             {.1f, 1.f, .1f},
             {1.f, 1.f, .1f},
             {.1f, 1.f, 1.f},
             {1.f, 1.f, 1.f}  //
        };

        for (int i = 0; i < lightColors.size(); i++) {
            auto pointLight = GameObject::MakePointLight(0.2f);
            pointLight.color = lightColors[i];
            auto rotateLight = glm::rotate(
                glm::mat4(1.f),
                (i * glm::two_pi<float>()) / lightColors.size(),
                { 0.f, -1.f, 0.f });
            pointLight.transform.translation = glm::vec3(rotateLight * glm::vec4(-1.f, -1.f, -1.f, 1.f));
            m_GameObjects.emplace(pointLight.GetId(), std::move(pointLight));
        }
    }

}